﻿using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Collections.Generic;
using System.Text;

using System.Web.Configuration;
using System.Net;
using System.Xml;
using System.IO;
using System.Data.SqlClient;

namespace MyPCTagging.Web
{
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class LocationService
    {
        /* Read from file "Web.config" */
        private static String myKey = WebConfigurationManager.AppSettings["MyEricssonKey"];

        [OperationContract]
        /*
         * Return locations 
         */
        public List<Location> GetLocations(List<int> cidList, List<int> lacList)
        {
            if (cidList==null || lacList==null)return null;
     
            double lat, lng;            
            List<Location> locList = new List<Location>();
            for (int i = 0; i < cidList.Count; i++ )
            {
                // invalid CellID
                if (cidList[i] == 0)
                {
                    locList.Add(null);
                    continue;
                }
                // try to translate
                //bool flag = GetLatLng_G(cidList[i], lacList[i], out lat, out lng);
                bool flag = CheckDatabaseBuffer(cidList[i], lacList[i], out lat, out lng);
                if (flag)
                {
                    Location loc = new Location();
                    loc.latitude = lat;
                    loc.longitude = lng;
                    locList.Add(loc);
                } 
                else
                {
                    locList.Add(null);
                }
            }

            return locList;
        }

        /*
         * Check database buffer, or use Google&Ericsson
         * (return false if not got)
         */ 
        public static bool CheckDatabaseBuffer(int CID, int MAC, 
                                               out double lat, out double lng)
        {
            DatabaseService.InitConnection();

            // check database
            String cmdString = @"SELECT latitude,longitude FROM dbo.LocationBuffer
                                WHERE CID="+CID+" AND MAC="+MAC;
            SqlDataReader reader = DatabaseService.ExecuteReader(cmdString);
            if (reader != null && reader.Read())
            {
                if (reader["latitude"]!=null && reader["longitude"]!=null)
                {
                    lat = Convert.ToDouble(reader["latitude"]);
                    lng = Convert.ToDouble(reader["longitude"]);
                    return true;
                }
            }
            // not found in database
            bool flag = GetLatLng_G(CID, MAC, out lat, out lng);
            if (!flag) return false;
            // save in database
            cmdString = @"INSERT INTO dbo.LocationBuffer 
                          VALUES(" + CID + "," + MAC + "," + lat + "," + lng + ")";
            reader = DatabaseService.ExecuteReader(cmdString);
            if (reader != null && reader.Read())
            {
                //insert succeed
            }

            DatabaseService.CloseConnection();
            return true;
        }

        /*
         * Use CID, LAC, MCC and MNC
         * (According to Ericsson)
         */
        public static bool GetLatLng_E(int CID, int LAC, int MCC, int MNC,
                                     out double lat, out double lng)
        {
            String url = "http://cellid.labs.ericsson.net/xml/lookup?key=" + myKey + "&base=10";
            url += "&cellid=" + CID;
            url += "&lac=" + LAC;
            url += "&mcc=" + MCC;
            url += "&mnc=" + MNC;

            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(
                    new Uri(url));
            req.Method = "GET";

            //Get response
            HttpWebResponse res = (HttpWebResponse)req.GetResponse();
            byte[] tmpBytes = new byte[res.ContentLength];
            int totalBytesRead = 0;
            while (totalBytesRead < tmpBytes.Length)
            {
                totalBytesRead += res.GetResponseStream().Read(
                    tmpBytes, totalBytesRead, tmpBytes.Length - totalBytesRead);
            }

            //Check state and parse XML
            if (res.StatusCode == HttpStatusCode.OK)
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(Encoding.UTF8.GetString(tmpBytes));

                XmlNode latNode = doc.GetElementsByTagName("latitude").Item(0);
                XmlNode lngNode = doc.GetElementsByTagName("longitude").Item(0);

                lat = Convert.ToDouble(latNode.FirstChild.Value);
                lng = Convert.ToDouble(lngNode.FirstChild.Value);

                return true;
            }

            lat = 0;
            lng = 0;
            return false;
        }

        /*
         * Use CID, LAC ,MCC and MNC    
         * (According to Google)
         */
        public static bool GetLatLng_G(int CID, int LAC, int MCC, int MNC,
                                         out double lat, out double lng)
        {
            String url = "http://www.google.com/glm/mmap";
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(
                new Uri(url));
            req.Method = "POST";
            byte[] postData = { 0x00, 0x0e,  
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  
                            0x00, 0x00,  
                            0x00, 0x00,  
                            0x00, 0x00,  
 
                            0x1b,  
                            0x00, 0x00, 0x00, 0x00, // offset 0x11, MNC  
                            0x00, 0x00, 0x00, 0x00, // offset 0x15, MCC  
                            0x00, 0x00, 0x00, 0x00, // offset 0x19  
                            0x00, 0x00,  
                            0x00, 0x00, 0x00, 0x00, // offset 0x1f, MNC  
                            0x00, 0x00, 0x00, 0x00, // offset 0x23, MCC  
                            0x00, 0x00, 0x00, 0x00, // offset 0x27, CID  
                            0x00, 0x00, 0x00, 0x00, // offset 0x2b, LAC  
                            0xff, 0xff, 0xff, 0xff,  
                            0x00, 0x00, 0x00, 0x00 };

            // UTMS: 6 hex digits, GSM: 4 hex digits
            if ((Int64)CID > 65536)
                postData[0x1c] = 5;
            else
                postData[0x1c] = 3;

            // Fill parameters
            FillByte(postData, 0x11, MNC);
            FillByte(postData, 0x15, MCC);
            FillByte(postData, 0x27, MNC);
            FillByte(postData, 0x2b, MCC);
            FillByte(postData, 0x1f, CID);
            FillByte(postData, 0x23, LAC);

            req.ContentLength = postData.Length;
            req.ContentType = "application/binary";

            Stream outputStream = req.GetRequestStream();
            outputStream.Write(postData, 0, postData.Length);
            outputStream.Close();

            //Get response
            HttpWebResponse res = (HttpWebResponse)req.GetResponse();
            byte[] tmpBytes = new byte[res.ContentLength];
            int totalBytesRead = 0;
            while (totalBytesRead < tmpBytes.Length)
            {
                totalBytesRead += res.GetResponseStream().Read(
                    tmpBytes, totalBytesRead, tmpBytes.Length - totalBytesRead);
            }

            if (res.StatusCode == HttpStatusCode.OK)
            {
                int ret_code = (int)((tmpBytes[3] << 24) | (tmpBytes[4] << 16)
                    | (tmpBytes[5] << 8) | (tmpBytes[6]));
                if (ret_code == 0)
                {
                    lat = ((double)((tmpBytes[7] << 24) | (tmpBytes[8] << 16)
                        | (tmpBytes[9] << 8) | (tmpBytes[10]))) / 1000000;
                    lng = ((double)((tmpBytes[11] << 24) | (tmpBytes[12] << 16)
                        | (tmpBytes[13] << 8) | (tmpBytes[14]))) / 1000000;
                    return true;
                }
            }

            lat = 0;
            lng = 0;
            return false;
        }

        /*
         * Just use CID and LAC         
         * (According to Google)
         */
        public static bool GetLatLng_G(int CID, int LAC, out double lat, out double lng)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(
                "http://www.google.com/glm/mmap");
            request.Method = "POST";

            byte[] byteArray = { 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 
                             0x65, 0x6E,       // "en"
                             0x00, 0x07,
                             0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, // "Android" 
                             0x00, 0x03, 
                             0x31, 0x2E, 0x30, // "1.0"
                             0x00, 0x03, 
                             0x57, 0x65, 0x62, // "web"
                             0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                             0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 
                             0xFF, 0xFF, 0xFF, 0xFF, // here's CID
                             0xFF, 0xFF, 0xFF, 0xFF, // here's LAC
                             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

            //Write CellID and LAC
            FillByte(byteArray, 48, CID);
            FillByte(byteArray, 52, LAC);

            //Set request
            request.ContentLength = byteArray.Length;
            Stream postStream = request.GetRequestStream();
            postStream.Write(byteArray, 0, byteArray.Length);
            postStream.Close();

            //Get response
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            
            //Read response
            Stream dataStream = response.GetResponseStream();
            BinaryReader br = new BinaryReader(dataStream);

            //Skip 3 bytes
            br.ReadByte();
            br.ReadByte();
            br.ReadByte();

            //Check state
            if (0 == br.ReadInt32())
            {
                //Read lat
                byte[] tmpBytes = new byte[4];
                tmpBytes[3] = br.ReadByte();
                tmpBytes[2] = br.ReadByte();
                tmpBytes[1] = br.ReadByte();
                tmpBytes[0] = br.ReadByte();
                lat = (double)(BitConverter.ToInt32(tmpBytes, 0)) / 1000000;

                //Read lng
                tmpBytes[3] = br.ReadByte();
                tmpBytes[2] = br.ReadByte();
                tmpBytes[1] = br.ReadByte();
                tmpBytes[0] = br.ReadByte();
                lng = (double)(BitConverter.ToInt32(tmpBytes, 0)) / 1000000;

                br.Close();
                dataStream.Close();
                response.Close();
                return true;
            }
            else
            {
                br.Close();
                dataStream.Close();
                response.Close();
                lat = 0;
                lng = 0;
                return false;
            }
        }

        /*
         * Change integer to 4 bytes, with little endian
         */
        private static void FillByte(byte[] byteArray, int offset, int data)
        {
            byteArray[offset] = (byte)((data >> 24) & 0xFF);
            byteArray[offset + 1] = (byte)((data >> 16) & 0xFF);
            byteArray[offset + 2] = (byte)((data >> 8) & 0xFF);
            byteArray[offset + 3] = (byte)((data >> 0) & 0xFF);
        }
    }
}
